
#include "common.h"
#include "../util/util.hpp"
#include "../util/poll_set.hpp"                                                                                                                                                           
#include "../util/poll_manager.hpp"
#include "../transport/transport_kcp.hpp"

#include <queue>
#include <thread>
#include <condition_variable>
#include <mutex>

#include <stdlib.h>
#include <string>
#include <string.h>

#include <boost/make_shared.hpp>
#include <boost/smart_ptr.hpp>
#include <functional>
#include <assert.h>
#include <memory>
#include <unistd.h>


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>    

 
#define handle_error(msg) \
	do { perror(msg); exit(EXIT_FAILURE); } while(0)


void checkForShutdown()
{}


int
main(int argc, const char *argv[]){
  /*------------------------------------------------------------*/
	FILE *fp;
	int tcp_fd;
	int cfd;
	int addrlen;
	int size_number=0;
	ssize_t recv_size=0;	
	char recv_buf[10240+1]={0};
	//char recv_buf[1024+1]={0};

  std::queue<std::string> recv_queue;
  std::mutex cv_mutex;
  std::condition_variable cv_queue;

	
	//remote_addr.sin_addr.s_addr=inet_addr(argv[2]);
	//remote_addr.sin_port=htons(atoi(argv[3]));
	
	if(argc!=2){		
		fprintf(stderr,"Usage: %s <save_filename> \n",argv[0]);
		exit(EXIT_FAILURE);		
	}

  printf("file name to recv: %s\n", argv[1]);
	
	//if((fp = fopen(argv[1], "w+")) == NULL){
	if((fp = fopen(argv[1], "wb")) == NULL){
		handle_error("fopen");		
	}
	
  /*------------------------------------------------------------*/



  /*------------------------------------------------------------*/
  int servfd, clientfd;
  struct sockaddr_in serv_addr, client_addr;
  memset(&serv_addr, 0, sizeof(serv_addr));
  memset(&client_addr, 0, sizeof(client_addr));

  if((servfd = socket(AF_INET, SOCK_STREAM, 0))==-1)
  {
    INFO("create tcp socket failed: ", strerror(errno));
    return 0;
  }
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(6060);
  serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  if(bind (servfd, (sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
  {
    INFO("bind failed : ", strerror(errno));
    return 0;
  }
  if(listen(servfd, 1024)==-1)
  {
    INFO("listen failed : ", strerror(errno));
    return 0;
  }
  socklen_t client_size = 0;
  if((clientfd = accept(servfd, (sockaddr*)&client_addr, &client_size))==-1)
  {
    INFO("accept failed : ", strerror(errno));
    return 0;
  }
  char tcpbuf[1024];
  char tcpsd[1024];
  memset(tcpbuf, 0, sizeof(tcpbuf));
  memset(tcpsd, 0, sizeof(tcpsd));
  int rdstate = read(clientfd, tcpbuf, sizeof(tcpbuf));
  if(rdstate==0)
  {
    std::cout<<"client exit!"<<std::endl;
    return 0;
  }
  if(strncasecmp(tcpbuf, "SIZE:", 5)!=0)
  {
    std::cout<<"SIZE cmd wrong!"<<std::endl;
    return 0;
  }
  for(int i = 0; i < 5; i++) tcpbuf[i] = 0;
  strcpy(tcpbuf, tcpbuf+5);
  int file_size = atoi(tcpbuf);
  std::cout<<"SIZE : "<<file_size<<" Bytes"<<std::endl;

  sprintf(tcpsd, "OK");
  write(clientfd, tcpsd, strlen(tcpsd));

  //memset(tcpbuf, 0, sizeof(tcpbuf));
  shutdown(servfd, SHUT_RDWR);
  shutdown(clientfd, SHUT_RDWR);

  close(servfd);
  close(clientfd);

  /*------------------------------------------------------------*/
  KcpOpt opt;
  opt.conv                = 666;
  opt.interval            = 5;
  opt.snd_wnd_size        = 1024;
  opt.rcv_wnd_size        = 2048;
  opt.nodelay             = true;
  //opt.use_congestion      = true;
  //SetControl(new CongestionControl());
  opt.trigger_fast_resend = 10;
  opt.offline_standard    = 20000;
  opt.snd_queue_max_size  = 5000;
  Kcp<true>::ptr kcp_layer  = std::make_shared<Kcp<true>>(opt);

  int sock_kcp;
  sock_kcp = socket(AF_INET, SOCK_DGRAM, 0);

  sockaddr_in remoteaddr;   std::string remotehost = "127.0.0.1";   int remoteport = 9090;
  sockaddr_in localaddr;    std::string localhost = "127.0.0.1";    int localport = 8080;
                            
  remoteaddr.sin_family = AF_INET;                                                                                                            
  remoteaddr.sin_addr.s_addr = inet_addr(remotehost.c_str()); 
  remoteaddr.sin_port = htons(remoteport);

  localaddr.sin_family = AF_INET;                                                                                                            
  localaddr.sin_addr.s_addr = inet_addr(localhost.c_str()); 
  localaddr.sin_port = htons(localport);

  if(bind(sock_kcp, (sockaddr*)&localaddr, sizeof(localaddr))<0){INFO("sock_kcp bind failed.");}; 
  if(connect(sock_kcp, (sockaddr*)&remoteaddr, sizeof(remoteaddr))<0){INFO("sock_kcp connect failed.");};

    /*------------------------------------------------------------*/
    char kcp_tcpbuf[1024];                                                                                                                           
    char kcp_tcpsd[1024];
    memset(kcp_tcpbuf, 0, sizeof(kcp_tcpbuf));
    memset(kcp_tcpsd, 0, sizeof(kcp_tcpsd));

    int kcp_rdstate = read(sock_kcp, kcp_tcpbuf, sizeof(kcp_tcpbuf));
    if(kcp_rdstate<0){INFO("kcp read failed.");}
    if(kcp_rdstate==0) { std::cout<<"client exit!"<<std::endl; return 0; }
    std::cout<<kcp_tcpbuf<<std::endl;
    if(strncasecmp(kcp_tcpbuf, "HELLO KCP", 9)!=0)
    {
      std::cout<<"HELLOKCP wrong!"<<std::endl;
      return 0;
    }

    sprintf(kcp_tcpsd, "HI KCP");
    if(write(sock_kcp, kcp_tcpsd, strlen(kcp_tcpsd))<0) {INFO("kcp write failed.");}

    /*------------------------------------------------------------*/

  const int flags = fcntl(sock_kcp, F_GETFL, 0);
  if (flags < 0) {
      return flags;
  }
  if (flags & O_NONBLOCK) {
      return 0;
  }
  fcntl(sock_kcp, F_SETFL, flags | O_NONBLOCK);

  kcp_layer->SetSendFunc([&](const void *buf, std::size_t size, void*)
                          {
                            ssize_t len = sendto(sock_kcp, buf, size, 0,
                                                 (struct sockaddr*)&remoteaddr, sizeof(remoteaddr));
                            if(len == -1) //perror("sendto error");
                              INFO("[ERROR] sendto error");
                          });

  bool shutting_down = false;
  std::thread thread_kcpupdate([&]
                          {   
                            while(!shutting_down)
                            {
                              //INFO("KCP UPDATE");
                              if(kcp_layer->Update(iclock64()))
                              {
                                TRACE("UPDATE OFF LINE");
                              }
                              struct sockaddr_in client_addr; int addr_len;
                              uint8_t* buff_ptr = new uint8_t[1500];
                              int num_recv = recvfrom(sock_kcp, buff_ptr, 1500, 0, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len);
                              //if(num_recv<0)INFO("recvfrom failed!");
                              if(num_recv>0) 
                              {
                                kcp_layer->Input(buff_ptr, num_recv);
                                //kcp_layer->Flush();
                              }
                              delete [] buff_ptr;
                            }
                          });
  /*------------------------------------------------------------*/



  int begintime, endtime;
  begintime = clock();

  /*------------------------------------------------------------*/
	int write_sum=0;
	ssize_t write_size=0;	

  std::thread thread_write_file([&]{
      bool is_finished = false;
      while(1)
      {
        if(recv_queue.empty()){
          std::unique_lock<std::mutex> lk(cv_mutex);
          cv_queue.wait(lk, [&]{return !recv_queue.empty();});

          continue; 
        }

        while(!recv_queue.empty())
        {
          //------------------pop buf start-----------------------
          int buf_len = recv_queue.front().size();
          char buf_recv[buf_len];
          strcpy(buf_recv, recv_queue.front().c_str());
          recv_queue.pop();
          //------------------pop buf start-----------------------

          //------------------write file start-----------------------
		      if(fwrite(buf_recv, buf_len, 1, fp)<0){
		      	fclose(fp);
		      	fprintf(stderr,"Error %s file!\n", argv[1]);
		      	exit(EXIT_FAILURE);
		      }
          fflush(fp);
          //------------------write file end-----------------------
          
          write_sum += buf_len;
          procBar(write_sum, file_size);
          if(write_sum >= file_size){is_finished=true; break;}
        }
        if(is_finished==true)break;
      }
      printf("[INFO]write ok!\n");
      fflush(stdout);
    });

  /*------------------------------------------------------------*/

	while(1){
										
		//if((recv_size=udp_transport->read((unsigned char*)recv_buf, sizeof(recv_buf)-1))==-1){
		if( kcp_layer->Recv(recv_buf, sizeof(recv_buf)-1)<=0 )
    {
      continue;
		}


    recv_queue.push(string(recv_buf));

    if(recv_queue.size()==1){
      std::unique_lock<std::mutex> lk(cv_mutex);
      cv_queue.notify_all();
    }

		bzero(recv_buf, sizeof(recv_buf));

		size_number=size_number+recv_size;
    //procBar(size_number, file_size);
    if( size_number >= file_size )break;
		
	}

  /*------------------------------------------------------------*/
  thread_write_file.join();
  /*------------------------------------------------------------*/
  TRACE("recv and write ok!");
  fflush(stdout);

  endtime = clock();
  double proc_time = (double)(endtime - begintime)/(double)CLOCKS_PER_SEC;//sec
  double size_Mb = (((double)file_size*8)/(1024.0*1024.0));//Mbit
  double rate = size_Mb/proc_time;//Mbit/s
  printf("|:::::::::::::::::::::::::::|\n");
  printf("file size: %lf Mb\n", size_Mb);
  printf("use time: %lf s\n",proc_time);
  printf("rate: %lf Mbps\n", rate );                                                                                                           
  printf("|:::::::::::::::::::::::::::|\n");


  printf("enter while(1)...\n");
  fflush(stdout);


  while(1){};

  /*------------------------------------------------------------*/
  shutting_down = true;
  thread_kcpupdate.join();
  close(sock_kcp);
  /*------------------------------------------------------------*/



	
	fclose(fp);
  /*------------------------------------------------------------*/

	return 0;
}
